<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module" src="../../polymer-legacy.js"></script>
  <script type="module" src="./path-effects-elements.js"></script>
<body>

<script type="module">
import './path-effects-elements.js';
import { Polymer } from '../../lib/legacy/polymer-fn.js';
import { setOrderedComputed } from '../../lib/utils/settings.js';

setOrderedComputed(Boolean(window.location.search.match('orderedComputed')));

// Safari 9 has a horrible bug related to array enumeration after defining
// a non-enumerable property on it (we do for `.splices`); for now we work
// around chai's deepEqual implementation that enumerates array keys using
// `for in` in the specific case the bug causes an assert failure
function arraysEqual(a, b) {
  for (var i=0; i<a.length; i++) {
    if (a[i] !== b[i]) {
      throw new Error('expected ' + a + ' to equal ' + b);
    }
  }
  if (a.length != b.length) {
    throw new Error('expected ' + a + ' to equal ' + b);
  }
}

suite('basic path bindings', function() {

  var el;

  setup(function() {
    el = document.createElement('x-stuff');
    document.body.appendChild(el);
  });

  teardown(function() {
    document.body.removeChild(el);
  });

  function verifyObserverOutput(nestingLevel, nested, done) {
    assert.equal(el.computed, '[' + nested.obj.value + ']');
    assert.equal(el.nestedChanged.callCount, nestingLevel == 0 ? 1 : 0);
    if (nestingLevel == 0) {
      assert.equal(el.nestedChanged.firstCall.args[0], nested);
    }
    assert.equal(el.nestedSubpathChanged.callCount, 1);
    assert.equal(el.nestedObjChanged.callCount, nestingLevel <= 1 ? 1 : 0);
    if (nestingLevel <= 1) {
      assert.equal(el.nestedObjChanged.firstCall.args[0], nested.obj);
    }
    assert.equal(el.nestedObjSubpathChanged.callCount, 1);
    assert.equal(el.nestedObjSubpathChanged.firstCall.args[0].base, nested.obj);
    assert.equal(el.nestedObjValueChanged.callCount, 1);
    assert.equal(el.nestedObjValueChanged.firstCall.args[0], nested.obj.value);
    assert.equal(el.nestedSubpathChanged.callCount, 1);
    assert.equal(el.nestedSubpathChanged.firstCall.args[0].base, nested);
    assert.equal(el.$.compose.computed, '[' + nested.obj.value + ']');
    assert.equal(el.$.compose.objSubpathChanged.callCount, 1);
    assert.equal(el.$.compose.objSubpathChanged.firstCall.args[0].base, nested.obj);
    assert.equal(el.$.compose.objValueChanged.callCount, 1);
    assert.equal(el.$.compose.objValueChanged.firstCall.args[0], nested.obj.value);
    assert.equal(el.$.forward.computed, '[' + nested.obj.value + ']');
    assert.equal(el.$.forward.objSubpathChanged.callCount, 1);
    assert.equal(el.$.forward.objSubpathChanged.firstCall.args[0].base, nested.obj);
    assert.equal(el.$.forward.objValueChanged.callCount, 1);
    assert.equal(el.$.forward.objValueChanged.firstCall.args[0], nested.obj.value);
    assert.equal(el.$.basic.computed, '[' + nested.obj.value + ']');
    assert.equal(el.$.basic.notifyingValue, 42);
    assert.equal(el.$.basic.notifyingValue, 42);
    assert.equal(el.$.compose.$.basic1.computed, '[' + nested.obj.value + ']');
    assert.equal(el.$.compose.$.basic1.notifyingValue, 42);
    assert.equal(el.$.compose.$.basic2.computed, '[' + nested.obj.value + ']');
    assert.equal(el.$.compose.$.basic2.notifyingValue, 42);
    assert.equal(el.$.forward.$.compose.$.basic1.computed, '[' + nested.obj.value + ']');
    assert.equal(el.$.forward.$.compose.$.basic1.notifyingValue, 42);
    assert.equal(el.$.forward.$.compose.$.basic2.computed, '[' + nested.obj.value + ']');
    assert.equal(el.$.forward.$.compose.$.basic2.notifyingValue, 42);
    assert.equal(el.$.basic.getAttribute('attrvalue'), '42');
    assert.equal(el.$.compose.$.basic1.getAttribute('attrvalue'), '42');
    assert.equal(el.$.compose.$.basic2.getAttribute('attrvalue'), '42');
    assert.equal(el.$.forward.$.compose.$.basic1.getAttribute('attrvalue'), '42');
    assert.equal(el.$.forward.$.compose.$.basic2.getAttribute('attrvalue'), '42');
    assert.equal(el.$.pe.obj, nested.obj);
    switch (nestingLevel) {
      case 0:
        assert.equal(el.nestedSubpathChanged.firstCall.args[0].path, 'nested');
        assert.equal(el.nestedSubpathChanged.firstCall.args[0].value, nested);
        assert.equal(el.nestedObjSubpathChanged.firstCall.args[0].path, 'nested.obj');
        assert.equal(el.nestedObjSubpathChanged.firstCall.args[0].value, nested.obj);
        assert.equal(el.$.compose.objSubpathChanged.firstCall.args[0].path, 'obj');
        assert.equal(el.$.compose.objSubpathChanged.firstCall.args[0].value, nested.obj);
        assert.equal(el.$.forward.objSubpathChanged.firstCall.args[0].path, 'obj');
        assert.equal(el.$.forward.objSubpathChanged.firstCall.args[0].value, nested.obj);
        assert.equal(el.$.forward.$.compose.objSubpathChanged.firstCall.args[0].path, 'obj');
        assert.equal(el.$.forward.$.compose.objSubpathChanged.firstCall.args[0].value, nested.obj);
        // x-pe uses async PropertiesChanged
        setTimeout(() => {
          assert.equal(el.$.pe._propertiesChanged.callCount, 1);
          assert.equal(el.$.pe._propertiesChanged.firstCall.args[1].obj, nested.obj);
          done();
        });
        break;
      case 1:
        assert.equal(el.nestedSubpathChanged.firstCall.args[0].path, 'nested.obj');
        assert.equal(el.nestedSubpathChanged.firstCall.args[0].value, nested.obj);
        assert.equal(el.nestedObjSubpathChanged.firstCall.args[0].path, 'nested.obj');
        assert.equal(el.nestedObjSubpathChanged.firstCall.args[0].value, nested.obj);
        assert.equal(el.$.compose.objSubpathChanged.firstCall.args[0].path, 'obj');
        assert.equal(el.$.compose.objSubpathChanged.firstCall.args[0].value, nested.obj);
        assert.equal(el.$.forward.objSubpathChanged.firstCall.args[0].path, 'obj');
        assert.equal(el.$.forward.objSubpathChanged.firstCall.args[0].value, nested.obj);
        assert.equal(el.$.forward.$.compose.objSubpathChanged.firstCall.args[0].path, 'obj');
        assert.equal(el.$.forward.$.compose.objSubpathChanged.firstCall.args[0].value, nested.obj);
        // x-pe uses async PropertiesChanged
        setTimeout(() => {
          assert.equal(el.$.pe._propertiesChanged.callCount, 1);
          assert.equal(el.$.pe._propertiesChanged.firstCall.args[1].obj, nested.obj);
          done();
        });
        break;
      case 2:
        assert.equal(el.nestedSubpathChanged.firstCall.args[0].path, 'nested.obj.value');
        assert.equal(el.nestedSubpathChanged.firstCall.args[0].value, 42);
        assert.equal(el.nestedObjSubpathChanged.firstCall.args[0].path, 'nested.obj.value');
        assert.equal(el.nestedObjSubpathChanged.firstCall.args[0].value, 42);
        assert.equal(el.$.compose.objSubpathChanged.firstCall.args[0].path, 'obj.value');
        assert.equal(el.$.compose.objSubpathChanged.firstCall.args[0].value, 42);
        assert.equal(el.$.forward.objSubpathChanged.firstCall.args[0].path, 'obj.value');
        assert.equal(el.$.forward.objSubpathChanged.firstCall.args[0].value, 42);
        assert.equal(el.$.forward.$.compose.objSubpathChanged.firstCall.args[0].path, 'obj.value');
        assert.equal(el.$.forward.$.compose.objSubpathChanged.firstCall.args[0].value, 42);
        // x-pe uses async PropertiesChanged
        setTimeout(() => {
          // Note the PropertiesMixin element only sees a deep set to 'nested.obj.value'
          // because it overrides _shouldPropertyChange to allow object re-sets through
          assert.equal(el.$.pe._propertiesChanged.callCount, 1);
          assert.equal(el.$.pe._propertiesChanged.firstCall.args[1].obj, nested.obj);
          done();
        });
        break;
    }
  }

  test('downward data flow', function(done) {
    // Do the thing
    el.nested = {
      obj: {
        value: 42
      }
    };
    // Verify
    verifyObserverOutput(0, el.nested, done);
  });

  test('notification from basic element property change', function(done) {
    // Setup
    el.nested = {
      obj: {
        value: 41
      }
    };
    el.resetObservers();
    // Do the thing
    el.$.basic.notifyingValue = 42;
    // Verify
    verifyObserverOutput(2, el.nested, done);
  });

  test('notification from composed element property change', function(done) {
    // Setup
    el.nested = {
      obj: {
        value: 41
      }
    };
    el.resetObservers();
    // Do the thing
    el.$.compose.$.basic1.notifyingValue = 42;
    // Verify
    verifyObserverOutput(2, el.nested, done);
  });

  test('notification from forward\'s composed element property change', function(done) {
    // Setup
    el.nested = {
      obj: {
        value: 41
      }
    };
    el.resetObservers();
    // Do the thing
    el.$.forward.$.compose.$.basic1.notifyingValue = 42;
    // Verify
    verifyObserverOutput(2, el.nested, done);
  });

  test('notification from set in top element', function(done) {
    // Setup
    el.nested = {
      obj: {
        value: 41
      }
    };
    el.resetObservers();
    // Do the thing
    el.set('nested.obj.value', 42);
    // Verify
    verifyObserverOutput(2, el.nested, done);
  });

  test('notification from set in composed element', function(done) {
    // Setup
    el.nested = {
      obj: {
        value: 41
      }
    };
    el.resetObservers();
    // Do the thing
    el.$.compose.set('obj.value', 42);
    // Verify
    verifyObserverOutput(2, el.nested, done);
  });

  test('notification from set in forward element', function(done) {
    // Setup
    el.nested = {
      obj: {
        value: 41
      }
    };
    el.resetObservers();
    // Do the thing
    el.$.forward.set('obj.value', 42);
    // Verify
    verifyObserverOutput(2, el.nested, done);
  });

  test('notification from set in forward\'s composed element', function(done) {
    // Setup
    el.nested = {
      obj: {
        value: 41
      }
    };
    el.resetObservers();
    // Do the thing
    el.$.forward.$.compose.set('obj.value', 42);
    // Verify
    verifyObserverOutput(2, el.nested, done);
  });

  test('notification from object change in compose element', function(done) {
    // Setup
    el.nested = {
      obj: {
        value: 41
      }
    };
    el.resetObservers();
    // Do the thing
    el.$.compose.obj = {
      value: 42
    };
    // Verify
    verifyObserverOutput(1, el.nested, done);
  });

  test('notification from object change in forward element', function(done) {
    // Setup
    el.nested = {
      obj: {
        value: 41
      }
    };
    el.resetObservers();
    // Do the thing
    el.$.forward.obj = {
      value: 42
    };
    // Verify
    verifyObserverOutput(1, el.nested, done);
  });

  test('notification from object change in forward\'s compose element', function(done) {
    // Setup
    el.nested = {
      obj: {
        value: 41
      }
    };
    el.resetObservers();
    // Do the thing
    // Do the thing
    el.$.forward.$.compose.obj = {
      value: 42
    };
    // Verify
    verifyObserverOutput(1, el.nested, done);
  });

  test('cached paths get invalidated by object sets', function() {
    el.nested = {obj: { value: 'initial' }};
    assert.equal(el.$.forward.$.compose.$.basic1.notifyingValue, 'initial');
    el.$.forward.$.compose.$.basic1.notifyingValue = 'correct';
    assert.equal(el.$.forward.$.compose.$.basic1.notifyingValue, 'correct');
    el.nested = {obj: { value: 'wrong' }};
    assert.equal(el.$.forward.$.compose.$.basic1.notifyingValue, 'wrong');
    el.set('nested.obj.value', 'correct');
    assert.equal(el.$.forward.$.compose.$.basic1.notifyingValue, 'correct');
  });

  test('similarly named properties', function() {
    var nested = {
      obj: {
        value: 41,
        value2: 99
      }
    };
    el.nested = nested;
    assert.equal(el.$.compose.$.basic1.notifyingValue, 41);
    assert.equal(el.$.compose.$.basic1.othervalue, 99);
    el.set('nested.obj.value', 42);
    assert.equal(el.$.compose.$.basic1.notifyingValue, 42);
    assert.equal(el.$.compose.$.basic1.othervalue, 99);
    el.set('nested.obj.value2', 98);
    assert.equal(el.$.compose.$.basic1.notifyingValue, 42);
    assert.equal(el.$.compose.$.basic1.othervalue, 98);
  });

  suite('ancestors and descendants', function() {

    test('change base', function() {
      let c = {}, b = {c}, a = {b};
      el.a = a;
      assert.equal(el.aChanged.callCount, 1);
      assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a', base: a, value: a});
      assert.equal(el.abChanged.callCount, 1);
      assert.deepEqual(el.abChanged.firstCall.args[0], {path: 'a.b', base: b, value: b});
      assert.equal(el.abcChanged.callCount, 1);
      assert.deepEqual(el.abcChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
      assert.equal(el.abcxChanged.callCount, 1);
      assert.deepEqual(el.abcxChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
    });

    test('change subpath level 1', function() {
      let c = {}, b = {c}, a = {b};
      el.a = a;
      el.resetObservers();
      c = {};
      b = {c};
      el.set('a.b', b);
      assert.equal(el.aChanged.callCount, 1);
      assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a.b', base: a, value: b});
      assert.equal(el.abChanged.callCount, 1);
      assert.deepEqual(el.abChanged.firstCall.args[0], {path: 'a.b', base: b, value: b});
      assert.equal(el.abcChanged.callCount, 1);
      assert.deepEqual(el.abcChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
      assert.equal(el.abcxChanged.callCount, 1);
      assert.deepEqual(el.abcxChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
    });

    test('change subpath level 2', function() {
      let c = {}, b = {c}, a = {b};
      el.a = a;
      el.resetObservers();
      c = {};
      el.set('a.b.c', c);
      assert.equal(el.aChanged.callCount, 1);
      assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a.b.c', base: a, value: c});
      assert.equal(el.abChanged.callCount, 1);
      assert.deepEqual(el.abChanged.firstCall.args[0], {path: 'a.b.c', base: b, value: c});
      assert.equal(el.abcChanged.callCount, 1);
      assert.deepEqual(el.abcChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
      assert.equal(el.abcxChanged.callCount, 1);
      assert.deepEqual(el.abcxChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
    });

    test('change subpath of base only', function() {
      let c = {}, b = {c}, a = {b};
      el.a = a;
      el.resetObservers();
      let d = {};
      el.set('a.d', d);
      assert.equal(el.aChanged.callCount, 1);
      assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a.d', base: a, value: d});
      assert.equal(el.abChanged.callCount, 0);
      assert.equal(el.abcChanged.callCount, 0);
      assert.equal(el.abcxChanged.callCount, 0);
    });

    test('change subpath of level 1 only', function() {
      let c = {}, b = {c}, a = {b};
      el.a = a;
      el.resetObservers();
      let e = {};
      el.set('a.b.e', e);
      assert.equal(el.aChanged.callCount, 1);
      assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a.b.e', base: a, value: e});
      assert.equal(el.abChanged.callCount, 1);
      assert.deepEqual(el.abChanged.firstCall.args[0], {path: 'a.b.e', base: b, value: e});
      assert.equal(el.abcChanged.callCount, 0);
      assert.equal(el.abcxChanged.callCount, 0);
    });
    
    test('change subpath of level 2', function() {
      let c = {}, b = {c}, a = {b};
      el.a = a;
      el.resetObservers();
      let f = {};
      el.set('a.b.c.f', f);
      assert.equal(el.aChanged.callCount, 1);
      assert.deepEqual(el.aChanged.firstCall.args[0], {path: 'a.b.c.f', base: a, value: f});
      assert.equal(el.abChanged.callCount, 1);
      assert.deepEqual(el.abChanged.firstCall.args[0], {path: 'a.b.c.f', base: b, value: f});
      assert.equal(el.abcChanged.callCount, 1);
      assert.deepEqual(el.abcChanged.firstCall.args[0], {path: 'a.b.c.f', base: c, value: f});
      assert.equal(el.abcxChanged.callCount, 1);
      assert.deepEqual(el.abcxChanged.firstCall.args[0], {path: 'a.b.c.f', base: c, value: f});
    });
    
    test('change non-wildcard argument', function() {
      let c = {}, b = {c}, a = {b};
      el.a = a;
      el.resetObservers();
      let x = el.x = {};
      assert.equal(el.aChanged.callCount, 0);
      assert.equal(el.abChanged.callCount, 0);
      assert.equal(el.abcChanged.callCount, 0);
      assert.equal(el.abcxChanged.callCount, 1);
      assert.deepEqual(el.abcxChanged.firstCall.args[0], {path: 'a.b.c', base: c, value: c});
      assert.equal(el.abcxChanged.firstCall.args[1], x);
    });
    
  });

});

suite('path effects', function() {

  var el;

  setup(function() {
    el = document.createElement('x-stuff');
    document.body.appendChild(el);
  });

  teardown(function() {
    document.body.removeChild(el);
  });

  test('observer with multiple args, object last', function() {
    // Setup
    var nested = {
      obj: {
        value: 42
      }
    };
    // Do the thing
    el.setProperties({
      a: 'a',
      b: 'b',
      nested: nested
    });
    // Verify
    assert.equal(el.multipleChanged.callCount, 1);
    assert.equal(el.multipleChanged.firstCall.args[0], 'a');
    assert.equal(el.multipleChanged.firstCall.args[1], 'b');
    assert.equal(el.multipleChanged.firstCall.args[2].path, 'nested.obj');
    assert.equal(el.multipleChanged.firstCall.args[2].value, nested.obj);
    assert.equal(el.multipleChanged.firstCall.args[2].base, nested.obj);
  });

  test('observer with multiple args, object not last', function() {
    // Setup
    var nested = {
      obj: {
        value: 42
      }
    };
    // Do the thing
    el.setProperties({
      a: 'a',
      nested: nested,
      b: 'b'
    });
    // Verify
    assert.equal(el.multipleChanged.callCount, 1);
    assert.equal(el.multipleChanged.firstCall.args[0], 'a');
    assert.equal(el.multipleChanged.firstCall.args[1], 'b');
    assert.equal(el.multipleChanged.firstCall.args[2].path, 'nested.obj');
    assert.equal(el.multipleChanged.firstCall.args[2].value, nested.obj);
    assert.equal(el.multipleChanged.firstCall.args[2].base, nested.obj);
  });

  test('observer with multiple args, object first, then last', function() {
    // Setup
    var nested = {
      obj: {
        value: 42
      }
    };
    el.nested = nested;
    el.resetObservers();
    // Do the thing
    el.setProperties({
      a: 'a',
      b: 'b'
    });
    // Verify
    assert.equal(el.multipleChanged.callCount, 1);
    assert.equal(el.multipleChanged.firstCall.args[0], 'a');
    assert.equal(el.multipleChanged.firstCall.args[1], 'b');
    assert.equal(el.multipleChanged.firstCall.args[2].path, 'nested.obj');
    assert.equal(el.multipleChanged.firstCall.args[2].value, nested.obj);
    assert.equal(el.multipleChanged.firstCall.args[2].base, nested.obj);

    // Do another thing
    el.set('nested.obj.value', 43);
    // Verify
    assert.equal(el.multipleChanged.callCount, 2);
    assert.equal(el.multipleChanged.secondCall.args[0], 'a');
    assert.equal(el.multipleChanged.secondCall.args[1], 'b');
    assert.equal(el.multipleChanged.secondCall.args[2].path, 'nested.obj.value');
    assert.equal(el.multipleChanged.secondCall.args[2].value, 43);
    assert.equal(el.multipleChanged.secondCall.args[2].base, nested.obj);
  });

  test('observer & computed with multiple path args', function() {
    // Setup
    var nested = {
      b: 33,
      obj: {
        c: 66
      }
    };
    var a = 1;
    // Do the thing
    el.setProperties({
      a: a,
      nested: nested
    });
    // Verify
    assert.equal(el.multiplePathsChanged.callCount, 1);
    assert.equal(el.multiplePathsChanged.firstCall.args[0], 1);
    assert.equal(el.multiplePathsChanged.firstCall.args[1], 33);
    assert.equal(el.multiplePathsChanged.firstCall.args[2], 66);
    assert.equal(el.computedFromPaths, 100);
    assert.equal(el.$.boundChild.computedFromPaths, 100);

    el.set('a', a + 10);
    assert.equal(el.$.boundChild.computedFromPaths, 110);

    el.set('nested.b', nested.b + 10);
    assert.equal(el.$.boundChild.computedFromPaths, 120);

    el.set('nested.obj.c', nested.obj.c + 10);
    assert.equal(el.$.boundChild.computedFromPaths, 130);
  });

  test('array.splices notified', function() {
    el.arrayNoColl = [];
    assert.equal(el.arrayNoCollChanged.callCount, 1);
    el.push('arrayNoColl', 1, 2, 3);
    assert.equal(el.arrayNoCollChanged.callCount, 2);
    el.push('arrayNoColl', 4, 5, 6);
    assert.equal(el.arrayNoCollChanged.callCount, 3);
  });

  test('ensure splices sent into userland dont get nulled', function(){
    el.array = [];
    el.push('array', 1, 2, 3);
    assert.notEqual(el.arraySplices.indexSplices, null);
  });

  test('array.splices notified, multiple args, prop first', function() {
    var array = [];
    el.setProperties({
      array: array,
      prop: 'first'
    });
    assert.equal(el.arrayOrPropChanged.callCount, 1);
    el.push('array', 1, 2, 3);
    assert.equal(el.arrayOrPropChanged.callCount, 2);
    el.push('array', 4, 5, 6);
    assert.equal(el.arrayOrPropChanged.callCount, 3);
  });

  test('array.splices notified, multiple args, prop last', function() {
    el.array = [];
    el.push('array', 1, 2, 3);
    el.prop = 'last';
    // Twice for array (.splices & .length), once for prop
    assert.equal(el.arrayOrPropChanged.callCount, 3);
  });

  test('array.length notified', function() {
    el.data = [];
    assert.equal(el.$.boundChild.arrayLength, el.data.length);
    el.push('data', 1, 2, 3);
    assert.equal(el.$.boundChild.arrayLength, el.data.length);
    el.pop('data');
    el.pop('data');
    assert.equal(el.$.boundChild.arrayLength, el.data.length);
    el.unshift('data', 4, 5);
    el.unshift('data', 6, 7, 8);
    assert.equal(el.$.boundChild.arrayLength, el.data.length);
    el.splice('data', 9, 10, 11, 12, 13);
    assert.equal(el.$.boundChild.arrayLength, el.data.length);
    el.shift('data');
    el.shift('data');
    el.shift('data');
    assert.equal(el.$.boundChild.arrayLength, el.data.length);
  });

  test('splice correctly deletes full array if only 1 argument is passed in', function() {
    el.data = [1,2,3];
    assert.equal(el.data.length, 3);
    el.splice('data', 0);
    assert.equal(el.data.length, 0);
  });

});

suite('path API', function() {

  var el;

  setup(function() {
    el = document.createElement('x-stuff');
    document.body.appendChild(el);
  });

  teardown(function() {
    document.body.removeChild(el);
  });

  test('get', function() {
    el.simple = 11;
    el.nested = {
      again: {
        again: {
          wayOverThere: 99
        },
        there: 55
      },
      here: 42
    };
    assert.equal(el.get('simple'), 11);
    assert.equal(el.get('nested'), el.nested);
    assert.equal(el.get('nested.here'), 42);
    assert.equal(el.get(['nested', 'again']), el.nested.again);
    assert.equal(el.get('nested.again.there'), 55);
    assert.equal(el.get('nested.again.again'), el.nested.again.again);
    assert.equal(el.get(['nested', 'again.again', 'wayOverThere']), 99);
  });

  test('set', function() {
    el.set('simple', 11);
    el.set('nested', {});
    el.set('nested.here', 42);
    el.set(['nested', 'again'], {});
    el.set('nested.again.there', 55);
    el.set('nested.again.again', {});
    el.set(['nested', 'again.again', 'wayOverThere'], 99);
    el.set(['again.thereToo'], 111, el.nested.again);
    assert.equal(el.simple, 11);
    assert.equal(el.get('simple'), 11);
    assert.equal(el.get('nested'), el.nested);
    assert.equal(el.nested.here, 42);
    assert.equal(el.get('nested.here'), 42);
    assert.equal(el.get('nested.again'), el.nested.again);
    assert.equal(el.nested.again.there, 55);
    assert.equal(el.get('nested.again.there'), 55);
    assert.equal(el.get('nested.again.again'), el.nested.again.again);
    assert.equal(el.nested.again.again.wayOverThere, 99);
    assert.equal(el.get('nested.again.again.wayOverThere'), 99);
    assert.equal(el.nested.again.again.thereToo, 111);
    assert.equal(el.get('nested.again.again.thereToo'), 111);
  });

  test('notifyPath basic', function() {
    el.a = {b: {c: 0}};
    el.resetObservers();
    el.a.b.c = 55;
    el.notifyPath('a.b.c');
    assert.isTrue(el.aChanged.calledOnce);
    assert.equal(el.aChanged.firstCall.args[0].path, 'a.b.c');
    assert.equal(el.aChanged.firstCall.args[0].value, 55);
    assert.equal(el.get('a.b.c'), 55);
  });

  test('notifyPath with array path argument', function() {
    el.a = {b: {c: 0}};
    el.resetObservers();
    el.a.b.c = 56;
    el.notifyPath(['a', 'b.c']);
    assert.isTrue(el.aChanged.calledOnce);
    assert.equal(el.aChanged.firstCall.args[0].path, 'a.b.c');
    assert.equal(el.aChanged.firstCall.args[0].value, 56);
    assert.equal(el.get('a.b.c'), 56);
  });

  test('notifyPath with value argument', function() {
    el.a = {b: {c: 0}};
    el.resetObservers();
    el.a.b.c = 57;
    el.notifyPath('a.b.c', 57);
    assert.isTrue(el.aChanged.calledOnce);
    assert.equal(el.aChanged.firstCall.args[0].path, 'a.b.c');
    assert.equal(el.aChanged.firstCall.args[0].value, 57);
    assert.equal(el.get('a.b.c'), 57);
  });

  test('notifyPath with value argument and array path argument', function() {
    el.a = {b: {c: 0}};
    el.resetObservers();
    el.a.b.c = 57;
    el.notifyPath(['a', 'b.c'], 57);
    assert.isTrue(el.aChanged.calledOnce);
    assert.equal(el.aChanged.firstCall.args[0].path, 'a.b.c');
    assert.equal(el.aChanged.firstCall.args[0].value, 57);
    assert.equal(el.get('a.b.c'), 57);
  });

  test('notifyPath a non-extistant does nothing', function() {
    el.notifyPath('does.not.exist', true);
  });

  test('get array', function() {
    el.arrayChanged = function() {};
    el.array = [1, 2, 3];
    el.array.array = [5, 6, 7];
    el.array.prop = 'prop';
    assert.equal(el.get('array'), el.array);
    assert.equal(el.get('array.0'), 1);
    assert.equal(el.get('array.1'), 2);
    assert.equal(el.get('array.2'), 3);
    assert.equal(el.get('array.array'), el.array.array);
    assert.equal(el.get('array.array.0'), 5);
    assert.equal(el.get('array.array.1'), 6);
    assert.equal(el.get('array.array.2'), 7);
    assert.equal(el.get('array.prop'), 'prop');
    el.unshift('array', 0);
    el.splice('array', 2, 0, 1.5);
    el.unshift('array.array', 4);
    el.splice('array.array', 2, 0, 5.5);
    assert.equal(el.get('array.0'), 0);
    assert.equal(el.get('array.1'), 1);
    assert.equal(el.get('array.2'), 1.5);
    assert.equal(el.get('array.3'), 2);
    assert.equal(el.get('array.4'), 3);
    assert.equal(el.get('array.array.0'), 4);
    assert.equal(el.get('array.array.1'), 5);
    assert.equal(el.get('array.array.2'), 5.5);
    assert.equal(el.get('array.array.3'), 6);
    assert.equal(el.get('array.array.4'), 7);
  });

  test('set array', function() {
    el.arrayChanged = function() {};
    el.set('array', [1, 2, 3]);
    el.set('array.array', [5, 6, 7]);
    el.set('array.prop', 'prop');
    assert.equal(el.get('array'), el.array);
    assert.equal(el.get('array.0'), 1);
    assert.equal(el.get('array.1'), 2);
    assert.equal(el.get('array.2'), 3);
    assert.equal(el.get('array.array'), el.array.array);
    assert.equal(el.get('array.array.0'), 5);
    assert.equal(el.get('array.array.1'), 6);
    assert.equal(el.get('array.array.2'), 7);
    assert.equal(el.get('array.prop'), 'prop');
    el.set(['array', 0], 81);
    el.set(['array', 1], 82);
    el.set(['array', 2], 83);
    el.set(['array.array', 0], 91);
    el.set(['array.array', 1], 92);
    el.set(['array.array', 2], 93);
    el.set('array.prop', 'foo');
    assert.equal(el.get('array.0'), 81);
    assert.equal(el.get('array.1'), 82);
    assert.equal(el.get('array.2'), 83);
    assert.equal(el.get('array.array.0'), 91);
    assert.equal(el.get('array.array.1'), 92);
    assert.equal(el.get('array.array.2'), 93);
    assert.equal(el.get('array.prop'), 'foo');
    // el.set(['array', '#0'], 31);
    // el.set(['array', '#1'], 32);
    // el.set(['array', '#2'], 33);
    // el.set(['array.array', '#0'], 41);
    // el.set(['array.array', '#1'], 42);
    // el.set(['array.array', '#2'], 43);
  });

  var nop = function() {};

  test('push array', function() {
    el.arrayChanged = nop;
    el.array = ['orig1', 'orig2', 'orig3'];
    // var key = el.array.length;
    el.arrayChanged = function(change) {
      assert.strictEqual(change.indexSplices.length, 1);
      assert.strictEqual(change.indexSplices[0].index, 3);
      assert.strictEqual(change.indexSplices[0].addedCount, 2);
      assert.strictEqual(change.indexSplices[0].removed.length, 0);
    };
    var ret = el.push('array', 'new1', 'new2');
    assert.strictEqual(ret, 5);
    assert.strictEqual(el.array.length, 5);
    assert.strictEqual(el.array[0], 'orig1');
    assert.strictEqual(el.array[1], 'orig2');
    assert.strictEqual(el.array[2], 'orig3');
    assert.strictEqual(el.array[3], 'new1');
    assert.strictEqual(el.array[4], 'new2');
  });

  test('pop array', function() {
    el.arrayChanged = nop;
    el.array = ['orig1', 'orig2', 'orig3'];
    // var key = el.array.length-1;
    el.arrayChanged = function(change) {
      assert.strictEqual(change.indexSplices.length, 1);
      assert.strictEqual(change.indexSplices[0].index, 2);
      assert.strictEqual(change.indexSplices[0].addedCount, 0);
      assert.strictEqual(change.indexSplices[0].removed.length, 1);
      assert.strictEqual(change.indexSplices[0].removed[0], 'orig3');
    };
    var ret = el.pop('array');
    assert.strictEqual(ret, 'orig3');
    assert.strictEqual(el.array.length, 2);
    assert.strictEqual(el.array[0], 'orig1');
    assert.strictEqual(el.array[1], 'orig2');
  });

  test('unshift array', function() {
    el.arrayChanged = nop;
    el.array = ['orig1', 'orig2', 'orig3'];
    // var key = el.array.length;
    el.arrayChanged = function(change) {
      assert.strictEqual(change.indexSplices.length, 1);
      assert.strictEqual(change.indexSplices[0].index, 0);
      assert.strictEqual(change.indexSplices[0].addedCount, 2);
      assert.strictEqual(change.indexSplices[0].removed.length, 0);
    };
    var ret = el.unshift('array', 'new1', 'new2');
    assert.strictEqual(ret, 5);
    assert.strictEqual(el.array.length, 5);
    assert.strictEqual(el.array[0], 'new1');
    assert.strictEqual(el.array[1], 'new2');
    assert.strictEqual(el.array[2], 'orig1');
    assert.strictEqual(el.array[3], 'orig2');
    assert.strictEqual(el.array[4], 'orig3');
  });

  test('shift array', function() {
    el.arrayChanged = nop;
    el.array = ['orig1', 'orig2', 'orig3'];
    el.arrayChanged = function(change) {
      assert.strictEqual(change.indexSplices.length, 1);
      assert.strictEqual(change.indexSplices[0].index, 0);
      assert.strictEqual(change.indexSplices[0].addedCount, 0);
      assert.strictEqual(change.indexSplices[0].removed.length, 1);
      assert.strictEqual(change.indexSplices[0].removed[0], 'orig1');
    };
    var ret = el.shift('array');
    assert.strictEqual(ret, 'orig1');
    assert.strictEqual(el.array.length, 2);
    assert.strictEqual(el.array[0], 'orig2');
    assert.strictEqual(el.array[1], 'orig3');
  });

  test('splice array', function() {
    el.arrayChanged = nop;
    el.array = ['orig1', 'orig2', 'orig3'];
    // var key = el.array.length;
    el.arrayChanged = function(change) {
      assert.strictEqual(change.indexSplices.length, 1);
      assert.strictEqual(change.indexSplices[0].index, 1);
      assert.strictEqual(change.indexSplices[0].addedCount, 2);
      assert.strictEqual(change.indexSplices[0].removed.length, 1);
      assert.strictEqual(change.indexSplices[0].removed[0], 'orig2');
    };
    var ret = el.splice('array', 1, 1, 'new1', 'new2');
    assert.deepEqual(ret, ['orig2']);
    assert.strictEqual(el.array.length, 4);
    assert.strictEqual(el.array[0], 'orig1');
    assert.strictEqual(el.array[1], 'new1');
    assert.strictEqual(el.array[2], 'new2');
    assert.strictEqual(el.array[3], 'orig3');
  });

  test('corner: no-op push array', function() {
    el.arrayChanged = nop;
    el.array = ['orig1', 'orig2', 'orig3'];
    el.arrayChanged = function() {
      throw new Error("should not notify");
    };
    var ret = el.push('array');
    assert.deepEqual(ret, 3);
    assert.strictEqual(el.array.length, 3);
    assert.strictEqual(el.array[0], 'orig1');
    assert.strictEqual(el.array[1], 'orig2');
    assert.strictEqual(el.array[2], 'orig3');
  });

  test('corner: no-op pop array', function() {
    el.arrayChanged = nop;
    el.array = [];
    el.arrayChanged = function() {
      throw new Error("should not notify");
    };
    var ret = el.pop('array');
    assert.strictEqual(ret, undefined);
    assert.strictEqual(el.array.length, 0);
  });

  test('corner: no-op unshift array', function() {
    el.arrayChanged = nop;
    el.array = ['orig1', 'orig2', 'orig3'];
    el.arrayChanged = function() {
      throw new Error("should not notify");
    };
    var ret = el.unshift('array');
    assert.deepEqual(ret, 3);
    assert.strictEqual(el.array.length, 3);
    assert.strictEqual(el.array[0], 'orig1');
    assert.strictEqual(el.array[1], 'orig2');
    assert.strictEqual(el.array[2], 'orig3');
  });

  test('corner: no-op shift array', function() {
    el.arrayChanged = nop;
    el.array = [];
    el.arrayChanged = function() {
      throw new Error("should not notify");
    };
    var ret = el.shift('array');
    assert.strictEqual(ret, undefined);
    assert.strictEqual(ret, undefined);
    assert.strictEqual(el.array.length, 0);
  });

  test('corner: no-op splice array', function() {
    el.arrayChanged = nop;
    el.array = ['orig1', 'orig2', 'orig3'];
    el.arrayChanged = function() {
      throw new Error("should not notify");
    };
    var ret = el.splice('array');
    assert.deepEqual(ret, []);
    assert.strictEqual(el.array.length, 3);
    assert.strictEqual(el.array[0], 'orig1');
    assert.strictEqual(el.array[1], 'orig2');
    assert.strictEqual(el.array[2], 'orig3');
  });

  test('corner: splice array: string args', function() {
    el.arrayChanged = nop;
    el.array = ['orig1', 'orig2', 'orig3'];
    // var key = el.array.length;
    el.arrayChanged = function(change) {
      assert.strictEqual(change.indexSplices.length, 1);
      assert.strictEqual(change.indexSplices[0].index, 1);
      assert.strictEqual(change.indexSplices[0].addedCount, 2);
      assert.strictEqual(change.indexSplices[0].removed.length, 1);
      assert.strictEqual(change.indexSplices[0].removed[0], 'orig2');
    };
    var ret = el.splice('array', '1', '1', 'new1', 'new2');
    assert.deepEqual(ret, ['orig2']);
    assert.strictEqual(el.array.length, 4);
    assert.strictEqual(el.array[0], 'orig1');
    assert.strictEqual(el.array[1], 'new1');
    assert.strictEqual(el.array[2], 'new2');
    assert.strictEqual(el.array[3], 'orig3');
  });

  test('corner: splice array: negative start', function() {
    el.arrayChanged = nop;
    el.array = ['orig1', 'orig2', 'orig3'];
    // var key = el.array.length;
    el.arrayChanged = function(change) {
      assert.strictEqual(change.indexSplices.length, 1);
      assert.strictEqual(change.indexSplices[0].index, 1);
      assert.strictEqual(change.indexSplices[0].addedCount, 2);
      assert.strictEqual(change.indexSplices[0].removed.length, 1);
      assert.strictEqual(change.indexSplices[0].removed[0], 'orig2');
    };
    var ret = el.splice('array', '-2', '1', 'new1', 'new2');
    assert.deepEqual(ret, ['orig2']);
    assert.strictEqual(el.array.length, 4);
    assert.strictEqual(el.array[0], 'orig1');
    assert.strictEqual(el.array[1], 'new1');
    assert.strictEqual(el.array[2], 'new2');
    assert.strictEqual(el.array[3], 'orig3');
  });

  test('link two objects', function() {
    el.x = el.y = {};
    el.linkPaths('y', 'x');
    el.xChanged = sinon.spy();
    el.yChanged = sinon.spy();
    el.set('x.foo', 1);
    assert.equal(el.xChanged.callCount, 1);
    assert.equal(el.yChanged.callCount, 1);
    el.unlinkPaths('y');
    el.set('x.foo', 2);
    assert.equal(el.xChanged.callCount, 2);
    assert.equal(el.yChanged.callCount, 1);
  });

  test('link three objects', function() {
    el.x = el.y = el.a = {};
    el.linkPaths('y', 'x');
    el.linkPaths('a', 'x');
    el.xChanged = sinon.spy();
    el.yChanged = sinon.spy();
    el.aChanged = sinon.spy();
    el.set('x.foo', 1);
    assert.equal(el.xChanged.callCount, 1);
    assert.equal(el.yChanged.callCount, 1);
    assert.equal(el.aChanged.callCount, 1);
    el.set('a.foo', 2);
    assert.equal(el.xChanged.callCount, 2);
    assert.equal(el.yChanged.callCount, 2);
    assert.equal(el.aChanged.callCount, 2);
    el.unlinkPaths('y');
    el.set('a.foo', 3);
    assert.equal(el.xChanged.callCount, 3);
    assert.equal(el.yChanged.callCount, 2);
    assert.equal(el.aChanged.callCount, 3);
  });

  test('multiple linked dependencies to computed property', function() {
    let linkedObj = {prop: 'Linked'};
    el.computeFromLinkedPaths.reset();
    el.setProperties({
      linked1: linkedObj,
      linked2: linkedObj,
      a: 'A'
    });
    assert.equal(el.computeFromLinkedPaths.callCount, 1);
    assert.equal(el.computedFromLinkedPaths, 'ALinkedLinked');

    el.linkPaths('linked1', 'linked2');
    el.set('linked1.prop', 'Linked+');
    assert.equal(el.computeFromLinkedPaths.callCount, 2);
    assert.equal(el.computedFromLinkedPaths, 'ALinked+Linked+');
    el.linked3 = el.linked2;
    el.linkPaths('linked2', 'linked3');
    el.set('linked3.prop', 'Linked++');
    assert.equal(el.computeFromLinkedPaths.callCount, 3);
    assert.equal(el.computedFromLinkedPaths, 'ALinked++Linked++');
    el.set('linked2.prop', 'Linked+++');
    assert.equal(el.computeFromLinkedPaths.callCount, 4);
    assert.equal(el.computedFromLinkedPaths, 'ALinked+++Linked+++');
    el.set('linked1.prop', 'Linked++++');
    assert.equal(el.computeFromLinkedPaths.callCount, 5);
    assert.equal(el.computedFromLinkedPaths, 'ALinked++++Linked++++');

    el.linked4 = el.linked1;
    el.linkPaths('linked4', 'linked1');
    el.set('linked4.prop', 'Linked+++++');
    assert.equal(el.computeFromLinkedPaths.callCount, 6);
    assert.equal(el.computedFromLinkedPaths, 'ALinked+++++Linked+++++');
    el.set('linked3.prop', 'Linked++++++');
    assert.equal(el.computeFromLinkedPaths.callCount, 7);
    assert.equal(el.computedFromLinkedPaths, 'ALinked++++++Linked++++++');
    el.set('linked2.prop', 'Linked+++++++');
    assert.equal(el.computeFromLinkedPaths.callCount, 8);
    assert.equal(el.computedFromLinkedPaths, 'ALinked+++++++Linked+++++++');
    el.set('linked1.prop', 'Linked++++++++');
    assert.equal(el.computeFromLinkedPaths.callCount, 9);
    assert.equal(el.computedFromLinkedPaths, 'ALinked++++++++Linked++++++++');

    el.linked5 = el.linked3;
    el.linkPaths('linked5', 'linked3');
    el.set('linked4.prop', 'Linked+++++++++');
    assert.equal(el.computeFromLinkedPaths.callCount, 10);
    assert.equal(el.computedFromLinkedPaths, 'ALinked+++++++++Linked+++++++++');
    el.set('linked3.prop', 'Linked++++++++++');
    assert.equal(el.computeFromLinkedPaths.callCount, 11);
    assert.equal(el.computedFromLinkedPaths, 'ALinked++++++++++Linked++++++++++');
    el.set('linked2.prop', 'Linked+++++++++++');
    assert.equal(el.computeFromLinkedPaths.callCount, 12);
    assert.equal(el.computedFromLinkedPaths, 'ALinked+++++++++++Linked+++++++++++');
    el.set('linked1.prop', 'Linked++++++++++++');
    assert.equal(el.computeFromLinkedPaths.callCount, 13);
    assert.equal(el.computedFromLinkedPaths, 'ALinked++++++++++++Linked++++++++++++');

    el.unlinkPaths('linked4');
    el.set('linked4.prop', 'Linked+++++++++++++');
    assert.equal(el.computeFromLinkedPaths.callCount, 13);
    assert.equal(el.computedFromLinkedPaths, 'ALinked++++++++++++Linked++++++++++++');
  });

  test('link two arrays', function() {
    el.x = el.y = [];
    el.linkPaths('y', 'x');
    el.xChanged = sinon.spy();
    el.yChanged = sinon.spy();
    el.push('x', {});
    // 2 changes for arrays (splices & length)
    assert.equal(el.xChanged.callCount, 2);
    assert.equal(el.yChanged.callCount, 2);
    el.unlinkPaths('y');
    el.push('x', {});
    assert.equal(el.xChanged.callCount, 4);
    assert.equal(el.yChanged.callCount, 2);
  });

  test('link three arrays', function() {
    el.x = el.y = el.a = [];
    el.linkPaths('y', 'x');
    el.linkPaths('a', 'x');
    el.xChanged = sinon.spy();
    el.yChanged = sinon.spy();
    el.aChanged = sinon.spy();
    el.push('x', {});
    // 2 changes for arrays (splices & length)
    assert.equal(el.xChanged.callCount, 2);
    assert.equal(el.yChanged.callCount, 2);
    assert.equal(el.aChanged.callCount, 2);
    el.unlinkPaths('y');
    el.push('x', {});
    assert.equal(el.xChanged.callCount, 4);
    assert.equal(el.yChanged.callCount, 2);
    assert.equal(el.aChanged.callCount, 4);
  });

  test('get from path in deep observer', function() {
    el.arrayChanged = nop;
    var array = [1, 2, 3];
    // Initialize array
    el.arrayChangedDeep = function(info) {
      assert.strictEqual(el.get(info.path), array);
    };
    el.array = array;
    // Change index 0
    el.arrayChangedDeep = function(info) {
      assert.strictEqual(el.get(info.path), 99);
    };
    el.set('array.0', 99);
    // Unshift value
    el.arrayChangedDeep = function(info) {
      if (info.path == 'array.splices') {
        assert.strictEqual(el.get(info.path), array.splices);
      } else {
        assert.strictEqual(el.get(info.path), 4);
      }
    };
    el.unshift('array', 0);
    // Change index 0
    el.arrayChangedDeep = function(info) {
      assert.strictEqual(el.get(info.path), -1);
    };
    el.set('array.0', -1);
    // Verify array contents
    arraysEqual(el.array, [-1, 99, 2, 3]);
  });

});

suite('corner cases', function() {

  test('malformed observer has nice message on failure', function() {
    var thrown = false;
    var verifyError = function(e) {
      assert.equal(e.message, "Malformed observer expression 'foo(missingParenthesis'");
      thrown = true;
    };
    try {
      Polymer({
        is: 'x-broken',
        observers: ['foo(missingParenthesis']
      }).finalize();
    } catch (e) {
      verifyError(e);
    }

    assert.equal(thrown, true, "No exception thrown when parsing malformed observer");
  });

  test('reentry during path processing', function() {
    let host = document.createElement('x-reentry-host');
    document.body.appendChild(host);
    assert.equal(host.objChanged.callCount, 1);
    assert.equal(host.obj.prop, 0);
    host.set('obj.prop', host.obj.prop+1);
    assert.equal(host.objChanged.callCount, 2);
    assert.equal(host.objChanged.secondCall.args[0].path, 'obj.prop');
    assert.equal(host.objChanged.secondCall.args[0].value, 1);
  });

  test('reentry after wildcard observer queued', function() {
    let el = document.createElement('x-reentry-true');
    document.body.appendChild(el);
    assert.equal(el.propChanged.callCount, 2);
    assert.equal(el.propChanged.firstCall.args[0].base, true);
    assert.equal(el.propChanged.firstCall.args[0].value, true);
    assert.equal(el.propChanged.secondCall.args[0].base, true);
    assert.equal(el.propChanged.secondCall.args[0].value, true);
    document.body.removeChild(el);
  });

  // TODO(kschaaf): address code in `getArgValue` that looks in changedProps
  // when `undefined` for splices, which breaks this test (latent issue)
  // https://github.com/Polymer/polymer/issues/5479
  test.skip('reentry after prop goes back to undefined', function() {
    let el = document.createElement('x-reentry-undefined');
    document.body.appendChild(el);
    assert.equal(el.propChanged.callCount, 2);
    assert.equal(el.propChanged.firstCall.args[0].base, undefined);
    assert.equal(el.propChanged.firstCall.args[0].value, undefined);
    assert.equal(el.propChanged.secondCall.args[0].base, undefined);
    assert.equal(el.propChanged.secondCall.args[0].value, undefined);
    document.body.removeChild(el);
  });

  // TODO(kschaaf): address code in `getArgValue` that looks in changedProps
  // when `undefined` for splices, which breaks this test (latent issue)
  // https://github.com/Polymer/polymer/issues/5479
  test.skip('reentry after path goes back to undefined', function() {
    let el = document.createElement('x-reentry-undefined-path');
    document.body.appendChild(el);
    assert.equal(el.propChanged.callCount, 2);
    assert.equal(el.propChanged.firstCall.args[0].base, undefined);
    assert.equal(el.propChanged.firstCall.args[0].value, undefined);
    assert.equal(el.propChanged.secondCall.args[0].base, undefined);
    assert.equal(el.propChanged.secondCall.args[0].value, undefined);
    document.body.removeChild(el);
  });

  test('reentry from array splices', function() {
    let el = document.createElement('x-reentry-splices');
    document.body.appendChild(el);
    assert.equal(el.arrayChanged.callCount, 3);
    assert.deepEqual(el.arrayChanged.getCalls()[0].args[0].value, {
      indexSplices: [{ 
        index: 1, addedCount: 1, removed: [], object: el.array, type: 'splice'
      }]
    });
    assert.deepEqual(el.arrayChanged.getCalls()[1].args[0].value, {
      indexSplices: [{ 
        index: 0, addedCount: 1, removed: [], object: el.array, type: 'splice'
      }]
    });
    assert.deepEqual(el.arrayChanged.getCalls()[2].args[0].value, undefined);
    document.body.removeChild(el);
  });

  test('element without notify effects still notifies paths (1.x guarantee)', function() {
    let host = document.createElement('x-path-host');
    let listener = sinon.spy();
    host.addEventListener('obj-changed', listener);
    document.body.appendChild(host);
    assert.equal(host.objChanged.callCount, 1);
    assert.equal(host.obj.prop, 0);
    assert.equal(listener.callCount, 0);
    host.$.client.set('obj.prop', host.obj.prop+1);
    assert.equal(host.objChanged.callCount, 2);
    assert.equal(host.objChanged.secondCall.args[0].path, 'obj.prop');
    assert.equal(host.objChanged.secondCall.args[0].value, 1);
    assert.equal(listener.callCount, 1);
    assert.equal(listener.firstCall.args[0].detail.path, 'obj.prop');
    assert.equal(listener.firstCall.args[0].detail.value, 1);
  });

});
</script>

</body>
</html>
